var lodash = require('lodash');

// Promise Context object constructor.
function Context(resolve, reject, custom) {
    this.resolve = resolve;
    this.reject = reject;
    this.custom = custom;
}

// Default callback function - rejects on truthy error, otherwise resolves
function callback(ctx, err, result) {
    if (typeof ctx.custom === 'function') {
        var cust:any = function () {
            // Bind the callback to itself, so the resolve and reject
            // properties that we bound are available to the callback.
            // Then we push it onto the end of the arguments array.
            return ctx.custom.apply(cust, arguments);
        };
        cust.resolve = ctx.resolve;
        cust.reject = ctx.reject;
        cust.call(null, err, result);
    } else {
        if (err) {
            return ctx.reject({message:err});
        }
        ctx.resolve(result);
    }
}

var promisify:any = function (original, custom, thisArgs) {

    return function () {

        // Parse out the original arguments
        var args = Array.prototype.slice.call(arguments);

        // Return the promisified function
        return new Promise(function (resolve, reject) {

            // Create a Context object
            var ctx = new Context(resolve, reject, custom);

            // Append the callback bound to the context
            args.push(callback.bind(null, ctx));

            // Call the function
            original.apply(thisArgs ? thisArgs : original, args);
        });
    };
};
(<any>Promise).promisify = (<any>Promise).promisify || promisify;
(<any>Promise).promisifyRight = function (func) {
    return (<any>Promise).promisify(lodash.partialRight.apply(this, arguments));
};
(<any>Promise.prototype).callback = function (cb) {
    return this.then(function (data) {
        cb && cb(null, data);
        return data;
    }, function (data) {
        cb && cb(data);
        //throw data;
    });
};
(<any>Promise.prototype).finally = function finallyPolyfill(callback) {
    var constructor = this.constructor;

    return this.then(function (value) {
        return constructor.resolve(callback()).then(function () {
            return value;
        });
    }, function (reason) {
        return constructor.resolve(callback()).then(function () {
            throw reason;
        });
    });
};

(<any>Promise.prototype).callbackNoReturn = function (cb) {
    return this.then(function (data) {
        cb && cb(null);
        return data;
    }, function (data) {
        cb && cb(data);
        throw data;
    });
};

(<any>Promise).eachSeries = function (array, func) {
    var sequence = Promise.resolve([]);
    var items = [];
    array.forEach(function (item) {
        sequence = sequence.then(function () {
            var ret = func(item);
            items.push(ret);
            return ret;
        });
    });
    return sequence.then(function () {
        return Promise.all(items);
    });
};
(<any>Promise).delay = function (timeout) {
    return new Promise(function (resolve) {
        setTimeout(resolve, timeout);
    });
};

module.exports = promisify;
